﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ObjectTransposeApi.Structure;
using System.Reflection;
using ObjectTransposeApi.Helper;

namespace ObjectTransposeApi.Handler.Property
{
    /// <summary/>
    internal class NumberPropertyHandler : AbstractPropertyHandler
    {
        /// <summary/>
        private class InternalTypeMapper
        {
            /// <summary/>
            internal Type Source { get; set; }
            
            /// <summary/>
            internal Type Destination { get; set; }
            
            /// <summary/>
            internal Func<object, object> Convert { get; set; }

            /// <summary/>
            internal Func<object, bool> CanConvert { get; set; }
        }

        /// <summary/>
        internal override IEnumerable<QueueStructure> Process(PropertyStructure propertyStructure)
        {
            // read the source property value
            object sourceValue = propertyStructure.SourcePropertyInfo.GetValue(propertyStructure.Source, null);

            // if the value is not set then exit ... this is the last item in the chain
            if (sourceValue == null)
            {
                return base._successor.Process(propertyStructure);
            }

            List<InternalTypeMapper> internalTypeMappers = new List<InternalTypeMapper>();

            // add items
            internalTypeMappers.Add(new InternalTypeMapper() { Source = typeof(byte), Destination = typeof(short), Convert = value => Convert.ToInt16((byte)value), CanConvert = value => true });
            internalTypeMappers.Add(new InternalTypeMapper() { Source = typeof(byte), Destination = typeof(int), Convert = value => Convert.ToInt32((byte)value), CanConvert = value => true });
            internalTypeMappers.Add(new InternalTypeMapper() { Source = typeof(byte), Destination = typeof(long), Convert = value => Convert.ToInt64((byte)value), CanConvert = value => true });
            internalTypeMappers.Add(new InternalTypeMapper() { Source = typeof(short), Destination = typeof(byte), Convert = value => Convert.ToByte((short)value), CanConvert = value => (short)value <= (short)byte.MaxValue && (short)value >= (short)byte.MinValue });
            internalTypeMappers.Add(new InternalTypeMapper() { Source = typeof(short), Destination = typeof(int), Convert = value => Convert.ToInt32((short)value), CanConvert = value => true });
            internalTypeMappers.Add(new InternalTypeMapper() { Source = typeof(short), Destination = typeof(long), Convert = value => Convert.ToInt64((short)value), CanConvert = value => true });
            internalTypeMappers.Add(new InternalTypeMapper() { Source = typeof(int), Destination = typeof(byte), Convert = value => Convert.ToByte((int)value), CanConvert = value => (int)value <= (int)byte.MaxValue && (int)value >= (int)byte.MinValue });
            internalTypeMappers.Add(new InternalTypeMapper() { Source = typeof(int), Destination = typeof(short), Convert = value => Convert.ToInt16((int)value), CanConvert = value => (int)value <= (int)short.MaxValue && (int)value >= (int)short.MinValue });
            internalTypeMappers.Add(new InternalTypeMapper() { Source = typeof(int), Destination = typeof(long), Convert = value => Convert.ToInt64((int)value), CanConvert = value => true });
            internalTypeMappers.Add(new InternalTypeMapper() { Source = typeof(long), Destination = typeof(byte), Convert = value => Convert.ToByte((long)value), CanConvert = value => (long)value <= (long)byte.MaxValue && (long)value >= (long)byte.MinValue });
            internalTypeMappers.Add(new InternalTypeMapper() { Source = typeof(long), Destination = typeof(short), Convert = value => Convert.ToInt16((long)value), CanConvert = value => (long)value <= (long)short.MaxValue && (long)value >= (long)short.MinValue });
            internalTypeMappers.Add(new InternalTypeMapper() { Source = typeof(long), Destination = typeof(int), Convert = value => Convert.ToInt32((long)value), CanConvert = value => (long)value <= (long)int.MaxValue && (long)value >= (long)int.MinValue });

            // if nullable get the nullable type
            Type nullableDestinationType = NullableHelper.FetchNullableType(propertyStructure.DestinationPropertyInfo.PropertyType);
            Type nullableSourceType = NullableHelper.FetchNullableType(propertyStructure.SourcePropertyInfo.PropertyType);

            // find a match
            InternalTypeMapper internalTypeMapper = internalTypeMappers.Find(
                value => value.Source == propertyStructure.SourcePropertyInfo.PropertyType && value.Destination == propertyStructure.DestinationPropertyInfo.PropertyType);

            // if no match then check destination nullable
            if (internalTypeMapper == null)
            {
                internalTypeMapper = internalTypeMappers.Find(
                    value => value.Source == propertyStructure.SourcePropertyInfo.PropertyType && value.Destination == nullableDestinationType);
            }

            // if no match then check source nullable
            if (internalTypeMapper == null)
            {
                internalTypeMapper = internalTypeMappers.Find(
                    value => value.Source == nullableSourceType && value.Destination == propertyStructure.DestinationPropertyInfo.PropertyType);
            }

            // if no match then check both nullable
            if (internalTypeMapper == null)
            {
                internalTypeMapper = internalTypeMappers.Find(
                    value => value.Source == nullableSourceType && value.Destination == nullableDestinationType);
            }

            // was it NOT found
            if (internalTypeMapper == null)
            {
                return base._successor.Process(propertyStructure);
            }

            // if can convert then set
            if (internalTypeMapper.CanConvert(sourceValue))
            {
                propertyStructure.DestinationPropertyInfo.SetValue(propertyStructure.Destination, internalTypeMapper.Convert(sourceValue), null);
            }

            // nothing else to do
            return null;
        }
    }
}
